import { storeKey } from "./injectKey";
import { ModuleCollection } from "./module/module-collection";
import { genericSubscribe, installModule, resetStoreState } from "./store-util";

export class Store {
  constructor(options = {}) {
    // 将实列存储在 store
    const store = this;
    store._wrappedGetters = Object.create(null); // 计算属性
    // 格式化用户的传入参数(而vue-router则是将用户进行扁平化)
    store._modules = new ModuleCollection(options); // 格式化用户模块数据，有模块则将模式化数据存放在 _modules 下
    store._actions = Object.create(null);
    store._mutations = Object.create(null);

    store.strict = options.strict || false; // 是否开启严格模式
    store._committing = false; // 默认不是 commit 改变状态

    const state = this._modules.root.state; // 状态数据(包含根和各模块数据)
    // 安装模块状态,例如取模块内容(store.state.aMoudule.a)
    installModule(store, state, [], this._modules.root);

    // 将状态存放在 store 上变成响应式数据
    resetStoreState(store, state);

    store._subscribers = []; // 存放订阅函数

    // 遍历每个插件将仓库传入插件形形参
    options.plugins.forEach((plugin) => plugin(store));
  }

  // 用于判断状态是否在 mutations 方法中使用 commit 方法更改
  _withCommit(fn) {
    const committing = this._committing;
    this._committing = true;
    fn();
    this._committing = committing; // 此方法整体思想属于切片
  }

  subscribe(fn, options) {
    return genericSubscribe(fn, this._subscribers, options); // 添加订阅监听函数返回取消监听函数
  }

  get state() {
    return this._state.data;
  }

  // 贴换当前仓库状态
  replaceState(newState) {
    // 需要在严格模式下进行不然会警告提示
    this._withCommit(() => {
      this._state.data = newState;
    });
  }

  // commit 提交同步更新
  commit = (type, payload) => {
    const entry = this._mutations[type] || []; // 获取所有类型方法集合
    // 使用 commit 改变数据
    this._withCommit(() => {
      entry.forEach((handler) => handler(payload)); // 发布事件
    });

    // 遍历发布监听函数
    this._subscribers
      .slice()
      .forEach((sub) => sub({ type, payload }, this.state));
  };

  //! mutation 和 action 区别一个是异步方法 action 而同步方法 mutation
  //! action 方法中可以使用异步操作(action 中修改状态实际还是使用 commit 提交给 mutation 操作)返回的是一个 prmoise 而同步方法没用返回值
  //! 修改仓库中 state 状态只能在 mutation 中其他地方会报错提示

  // dispatch 派发异步方法
  dispatch = (type, payload) => {
    const entry = this._actions[type] || [];
    // console.log(entry, payload, type);
    return Promise.all(entry.map((handler) => handler(payload)));
  };

  // vue 注册插件
  install(app, injectKey) {
    app.provide(injectKey || storeKey, this); // 暴露一个全局变量，数据试 store 实列数据 (createApp().use(store,"xx"));
    app.config.globalProperties.$store = this; // 类似于 vue 实列上挂载 Vue.prototype.$store = this; 全局挂载
  }

  // 注册新模块
  registerModule(path, rawModule) {
    console.assert(
      Array.isArray(path),
      `module path must be a string or an Array.`
    );
    if (typeof path === "string") path = [path]; // 模块路径数组
    // 注册模块
    this._modules.register(rawModule, path);
    // 安装模块
    installModule(this, this.state, path, this._modules.get(path));
    // 重置状态
    resetStoreState(this, this.state);
  }
}

export function createStore(options) {
  return new Store(options); // 返回 store 实列
}
